﻿using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace BrickAndBrick
{
    /// <summary>
    /// 图片分析类，将游戏截图转为BBMap
    /// </summary>
    class BBPicAnalyse
    {
        public static BBMap GetBrickMap()
        {
            Mat src = BBCaptureWindow.CaptureWindow("砖了个砖");
            src = GetMapArea(src);
            Mat findContoursMat = src.Clone();

            Cv2.CvtColor(findContoursMat, findContoursMat, ColorConversionCodes.RGB2GRAY);
            Cv2.Threshold(findContoursMat, findContoursMat, 150, 255, ThresholdTypes.Binary);


            Cv2.FindContours(findContoursMat, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);

            int mapWidth = src.Width / 10;
            List<BBBrick> brickList = new List<BBBrick>();
            for (int i = 0; i < hierarchy.Length; i++)
            {
                Rect rect = Cv2.BoundingRect(contours[i]);
                int cutPara = rect.Width / 5;
                rect.X += cutPara;
                rect.Y += cutPara;
                rect.Width = rect.Width - cutPara - cutPara;
                rect.Height = rect.Height - cutPara - cutPara;

                //Bitmap temp = CropBitmap(bitmap, rect.X + 8, rect.Y + 8, 24, 24);
                Mat tempMat = src.Clone(rect);

                tempMat.SaveImage($@"C:\Users\user\Desktop\Test\{i}.png");
                brickList.Add(new BBBrick(tempMat, rect.X, rect.Y, mapWidth));
            }
            brickList = BBBrick.ComputeNums(brickList);

            return BrickListToMap( brickList);
        }

        /// <summary>
        /// 将BrickList转为BBMap
        /// </summary>
        /// <param name="brickList"></param>
        /// <returns></returns>
        private static BBMap BrickListToMap(List<BBBrick> brickList)
        {
            List<List<int>> mapList = new List<List<int>>();
            for (int i = 0; i < 14; i++)
            {
                List<int> temp = new List<int>();
                for (int j = 0; j < 10; j++)
                {
                    temp.Add(0);
                }
                mapList.Add(temp);
            }
            foreach (var item in brickList)
            {
                mapList[item.X][item.Y] = item.Num;
            }
            return new BBMap(mapList);
        }

        /// <summary>
        /// 从游戏窗口的截图中抠出砖块矩阵
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        static Mat GetMapArea(Mat src)
        {
            // 将图像从BGR转换到HSV
            Mat hsv = new Mat();
            Cv2.CvtColor(src, hsv, ColorConversionCodes.BGR2HSV);

            // 边框在HSV空间中的范围，这里需要根据实际情况调整
            Scalar lowerBrown = new Scalar(16.5, 150, 190);
            Scalar upperBrown = new Scalar(17, 200, 195);

            // 创建掩码来提取棕色区域
            Mat mask = new Mat();
            Cv2.InRange(hsv, lowerBrown, upperBrown, mask);

            // 使用掩码提取棕色区域
            Mat brownRegion = new Mat();
            src.CopyTo(brownRegion, mask);

            // 释放资源
            hsv.Dispose();
            mask.Dispose();

            Mat findContoursMat = brownRegion.Clone();

            Cv2.CvtColor(findContoursMat, findContoursMat, ColorConversionCodes.RGB2GRAY);
            Cv2.Threshold(findContoursMat, findContoursMat, 60, 255, ThresholdTypes.Binary);

            Cv2.FindContours(findContoursMat, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
            Rect rect = new Rect();
            for (int i = 0; i < hierarchy.Length; i++)
            {
                rect = Cv2.BoundingRect(contours[i]);
                if (rect.Width > src.Width * 0.9 && rect.Width < findContoursMat.Width)
                {
                    break;
                }
            }
            src = src.Clone(rect);
            int temp = src.Width / 70 - 2;
            rect = new Rect(temp, temp, src.Width - temp - temp, src.Height - temp - temp);
            src = src.Clone(rect);
            return src;
        }

        /// <summary>
        /// 截图类，提供截图相关方法
        /// </summary>
        class BBCaptureWindow
        {
            // 导入Windows API函数
            [DllImport("user32.dll", SetLastError = true)]
            static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

            [StructLayout(LayoutKind.Sequential)]
            struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }

            public static Mat CaptureWindow(string windowName)
            {
                // 查找窗口句柄
                IntPtr hWnd = FindWindow(null, windowName);
                if (hWnd == IntPtr.Zero)
                {
                    Console.WriteLine("窗口未找到!");
                    return null;
                }

                // 获取窗口的区域
                GetWindowRect(hWnd, out RECT rect);
                int width = rect.Right - rect.Left;
                int height = rect.Bottom - rect.Top;

                // 创建一个位图并在其中绘制窗口内容
                Bitmap bitmap = new Bitmap(width, height);
                Graphics g = Graphics.FromImage(bitmap);
                g.CopyFromScreen(rect.Left, rect.Top, 0, 0, new System.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);
                return BitmapConverter.ToMat(bitmap);
            }


        }
        /// <summary>
        /// 砖块类，提供图片对比相关方法，将相同砖块的数字置为相同
        /// </summary>
        class BBBrick
        {
            public int X;
            public int Y;
            Mat Mat;
            public int Num;

            public BBBrick(Mat mat, int rectX, int rectY, int brickWidth)
            {
                X = rectY / brickWidth;
                Y = rectX / brickWidth;
                this.Mat = mat;
            }
            /// <summary>
            /// 根据每个砖块的Mat图，将Mat相同的砖块的数字置为相同。
            /// </summary>
            /// <param name="brickList"></param>
            /// <returns></returns>
            public static List<BBBrick> ComputeNums(List<BBBrick> brickList)
            {
                int similarCount = 1;
                for (int i = 0; i < brickList.Count; i++)
                {
                    if (brickList[i].Num == 0)
                    {
                        for (int j = i + 1; j < brickList.Count; j++)
                        {
                            if (IsSimilar(brickList[i].Mat, brickList[j].Mat))
                            {
                                brickList[i].Num = brickList[j].Num = similarCount;
                            }
                        }
                        similarCount++;
                    }
                }
                return brickList;
            }

            const int M = 12;
            const double HSV_DIFFERENCE = 10;
            const double R_DIFFERENCE = 10;
            const double HASH_SIMILARITY = 0.0;
            const double HIST_SIMILARITY = 0.9;

            /// <summary>
            /// 判断两个Mat是否相似
            /// </summary>
            /// <param name="mat1"></param>
            /// <param name="mat2"></param>
            /// <returns></returns>
            static bool IsSimilar(Mat mat1, Mat mat2)
            {
                Cv2.Resize(mat1, mat1, new OpenCvSharp.Size(16, 16));
                Cv2.Resize(mat2, mat2, new OpenCvSharp.Size(16, 16));

                //hsv比较
                double hsv1 = CalcAveColor(mat1);
                double hsv2 = CalcAveColor(mat2);
                //Console.WriteLine("hsv:" + hsv1 + " " + hsv2);
                if (Math.Abs(hsv1 - hsv2) > HSV_DIFFERENCE)
                {
                    return false;
                }
                //红色比较
                double r1 = CalcAveR(mat1);
                double r2 = CalcAveR(mat2);
                //Console.WriteLine("R:" + r1 + " " + r2);
                if (Math.Abs(r1 - r2) > R_DIFFERENCE)
                {
                    return false;
                }
                //绿色比较
                double g1 = CalcAveG(mat1);
                double g2 = CalcAveG(mat2);
                //Console.WriteLine("G:" + r1 + " " + r2);
                if (Math.Abs(g1 - g2) > R_DIFFERENCE)
                {
                    return false;
                }
                //蓝色比较
                double b1 = CalcAveB(mat1);
                double b2 = CalcAveB(mat2);
                //Console.WriteLine("B:" + r1 + " " + r2);
                if (Math.Abs(b1 - b2) > R_DIFFERENCE)
                {
                    return false;
                }
                //哈希值比较
                /*
                Mat resizeMat1 = new Mat();
                Mat resizeMat2 = new Mat();
                Cv2.Resize(mat1, resizeMat1, new OpenCvSharp.Size(M, M));
                Cv2.Resize(mat2, resizeMat2, new OpenCvSharp.Size(M, M));

                string str1 = GetBitmapKey(resizeMat1);
                string str2 = GetBitmapKey(resizeMat2);
                int sum = 0;
                for (int i = 0; i < str1.Length; i++)
                {
                    if (str1[i] == str2[i])
                    {
                        sum++;
                    }
                }
                float result = (float)(sum * 1.0 / (M * M));
                Console.WriteLine("Hash:" + result);
                if (result < HASH_SIMILARITY)
                {
                    return false;
                }
                */
                //hist比较
                double hist = Compare_Hist(mat1, mat2);
                //Console.WriteLine("Hist:" + hist);
                if (hist < HIST_SIMILARITY)
                {
                    return false;
                }

                return true;
            }

            static String GetBitmapKey(Mat mat)
            {
                Mat grayMat = new Mat();
                Cv2.CvtColor(mat, grayMat, ColorConversionCodes.BGR2GRAY);

                double sum = 0;
                for (int i = 0; i < grayMat.Rows; i++)
                {
                    for (int j = 0; j < grayMat.Cols; j++)
                    {
                        sum += grayMat.At<byte>(i, j);
                    }
                }
                double avg = sum / (M * M);
                String result = "";
                for (int i = 0; i < grayMat.Rows; i++)
                {
                    for (int j = 0; j < grayMat.Cols; j++)
                    {
                        result += (grayMat.At<byte>(i, j) > avg) ? "1" : "0";
                    }
                }
                return result;
            }

            static double CalcAveColor(Mat image)
            {
                Mat hsvImage = new Mat();
                Cv2.CvtColor(image, hsvImage, ColorConversionCodes.BGR2HSV);

                // 计算每个通道的平均值
                Mat hChannel = hsvImage.Split()[0];
                Mat sChannel = hsvImage.Split()[1];
                Mat vChannel = hsvImage.Split()[2];

                Scalar hMean = Cv2.Mean(hChannel);
                Scalar sMean = Cv2.Mean(sChannel);
                Scalar vMean = Cv2.Mean(vChannel);

                // 清理资源
                hChannel.Dispose();
                sChannel.Dispose();
                vChannel.Dispose();
                hsvImage.Dispose();

                // 返回每个通道的平均值
                return new Scalar(hMean.Val0, sMean.Val0, vMean.Val0).ToDouble();
            }

            static double CalcAveR(Mat image)
            {

                // 初始化平均r值
                double avgR = 0;

                // 遍历图像的每个像素
                for (int y = 0; y < image.Height; y++)
                {
                    for (int x = 0; x < image.Width; x++)
                    {
                        // 获取像素值
                        Vec3b color = image.At<Vec3b>(y, x);

                        // 计算r值并累加
                        avgR += color.Item0;
                    }
                }

                // 计算平均r值
                avgR /= (image.Width * image.Height);

                return avgR;
            }

            static double CalcAveG(Mat image)
            {

                // 初始化平均r值
                double avgG = 0;

                // 遍历图像的每个像素
                for (int y = 0; y < image.Height; y++)
                {
                    for (int x = 0; x < image.Width; x++)
                    {
                        // 获取像素值
                        Vec3b color = image.At<Vec3b>(y, x);

                        // 计算g值并累加
                        avgG += color.Item1;
                    }
                }

                // 计算平均g值
                avgG /= (image.Width * image.Height);

                return avgG;
            }

            static double CalcAveB(Mat image)
            {

                // 初始化平均r值
                double avgB = 0;

                // 遍历图像的每个像素
                for (int y = 0; y < image.Height; y++)
                {
                    for (int x = 0; x < image.Width; x++)
                    {
                        // 获取像素值
                        Vec3b color = image.At<Vec3b>(y, x);

                        // 计算g值并累加
                        avgB += color.Item2;
                    }
                }

                // 计算平均g值
                avgB /= (image.Width * image.Height);

                return avgB;
            }

            //直方图比较
            static double Compare_Hist(Mat matA, Mat matB)
            {
                // 拆分通道
                Cv2.Split(matA, out Mat[] matA_S);
                Cv2.Split(matB, out Mat[] matB_S);
                //直方图的像素范围   
                Rangef[] histRange = { new Rangef(0, 256) };
                //直方图数组大小
                int[] histSize = { 256 };
                //直方图输出数组
                Mat hist_A = new Mat();
                Mat hist_B = new Mat();
                bool uniform = true, accumulate = false;
                Cv2.CalcHist(matA_S, new int[] { 0, 1, 2 }, null, hist_A, 1, histSize, histRange, uniform, accumulate);
                Cv2.CalcHist(matB_S, new int[] { 0, 1, 2 }, null, hist_B, 1, histSize, histRange, uniform, accumulate);
                //相关性比较
                var res = Cv2.CompareHist(hist_A, hist_B, HistCompMethods.Correl);
                return res;
            }
        }
    }
}
